import type {
  AxiosRequestConfig,
  AxiosInstance,
  AxiosResponse,
  AxiosError,
} from "axios";
import type { RequestOptions, Result, UploadFileParams } from "/#/axios";
import type { CreateAxiosOptions } from "./axiosTransform";
import axios from "axios";
import qs from "qs";

import { AxiosCanceler } from "./axiosCancel";
import { isFunction } from "@/utils/is";
import { cloneDeep } from "lodash-es";
import { ContentTypeEnum } from "@/enums/httpEnum";
import { RequestEnum } from "@/enums/httpEnum";

export * from "./axiosTransform";

/**
 * @description:  axios module
 */
export class VAxios {
  private axiosInstance: AxiosInstance;
  private readonly options: CreateAxiosOptions;

  constructor(options: CreateAxiosOptions) {
    this.options = options;
    this.axiosInstance = axios.create(options);
    this.setupInterceptors();
  }

  /**
   * @description:  Create axios instance
   */
  private createAxios(config: CreateAxiosOptions): void {
    this.axiosInstance = axios.create(config);
  }

  private getTransform() {
    const { transform } = this.options;
    return transform;
  }

  getAxios(): AxiosInstance {
    return this.axiosInstance;
  }

  /**
   * @description: Reconfigure axios
   */
  configAxios(config: CreateAxiosOptions) {
    if (!this.axiosInstance) {
      return;
    }
    this.createAxios(config);
  }

  /**
   * @description: Set general header
   */
  setHeader(headers: any): void {
    if (!this.axiosInstance) {
      return;
    }
    Object.assign(this.axiosInstance.defaults.headers, headers);
  }

  /**
   * @description: Interceptor configuration
   */
  private setupInterceptors() {
    const transform = this.getTransform();
    if (!transform) {
      return;
    }
    const {
      requestInterceptors,
      requestInterceptorsCatch,
      responseInterceptors,
      responseInterceptorsCatch,
    } = transform;

    const axiosCanceler = new AxiosCanceler();

    // Request interceptor configuration processing
    this.axiosInstance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        // If cancel repeat request is turned on, then cancel repeat request is prohibited
        const {
          // @ts-ignore
          headers: { ignoreCancelToken },
        } = config;

        const ignoreCancel =
          ignoreCancelToken !== undefined
            ? ignoreCancelToken
            : this.options.requestOptions?.ignoreCancelToken;

        !ignoreCancel && axiosCanceler.addPending(config);
        if (requestInterceptors && isFunction(requestInterceptors)) {
          config = requestInterceptors(config, this.options);
        }
        return config;
      },
      undefined,
    );

    // Request interceptor error capture
    requestInterceptorsCatch &&
      isFunction(requestInterceptorsCatch) &&
      this.axiosInstance.interceptors.request.use(
        undefined,
        requestInterceptorsCatch,
      );

    // Response result interceptor processing
    this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
      if (
        res.config.url ==
        "/api/system/system/v1/sys/sysdictcategory/queryItemByCode"
      ) {
        res.data.result.itemList = res.data.result.itemModelList;
      }
      res && axiosCanceler.removePending(res.config);
      if (responseInterceptors && isFunction(responseInterceptors)) {
        res = responseInterceptors(res);
      }
      return res;
    }, undefined);

    // Response result interceptor error capture
    responseInterceptorsCatch &&
      isFunction(responseInterceptorsCatch) &&
      this.axiosInstance.interceptors.response.use(
        undefined,
        responseInterceptorsCatch,
      );
  }

  /**
   * @description:  File Upload
   */
  uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
    const formData = new window.FormData();
    const customFilename = params.name || "file";

    if (params.filename) {
      formData.append(customFilename, params.file, params.filename);
    } else {
      formData.append(customFilename, params.file);
    }

    if (params.data) {
      Object.keys(params.data).forEach((key) => {
        const value = params.data![key];
        if (Array.isArray(value)) {
          value.forEach((item) => {
            formData.append(`${key}[]`, item);
          });
          return;
        }

        formData.append(key, params.data![key]);
      });
    }

    return this.axiosInstance.request<T>({
      ...config,
      method: "POST",
      data: formData,
      headers: {
        "Content-type": ContentTypeEnum.FORM_DATA,
        // @ts-ignore
        ignoreCancelToken: true,
      },
    });
  }

  // support form-data
  /**
   * 将请求数据转换为表单数据格式。
   * @param config Axios请求配置对象。
   * @returns 转换后的配置对象。
   */
  supportFormData(config: AxiosRequestConfig) {
    /**
     * This code checks the content type of the request and the request method to determine if the request data needs to be transformed.
     * If the content type is not "application/x-www-form-urlencoded" or if the request method is GET, the code returns the original configuration.
     * Otherwise, it converts the request data to a URL-encoded string using the "qs" library with the "brackets" array format.
     * The modified configuration is then returned.
     */
    const headers = config.headers || this.options.headers;
    const contentType = headers?.["Content-Type"] || headers?.["content-type"];
    if (
      contentType !== ContentTypeEnum.FORM_URLENCODED ||
      !Reflect.has(config, "data") ||
      config.method?.toUpperCase() === RequestEnum.GET
    ) {
      return config;
    }
    return {
      ...config,
      data: qs.stringify(config.data, { arrayFormat: "brackets" }),
    };
  }

  get<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions,
  ): Promise<T> {
    return this.request({ ...config, method: "GET" }, options);
  }

  post<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions,
  ): Promise<T> {
    return this.request({ ...config, method: "POST" }, options);
  }

  put<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions,
  ): Promise<T> {
    return this.request({ ...config, method: "PUT" }, options);
  }

  delete<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions,
  ): Promise<T> {
    return this.request({ ...config, method: "DELETE" }, options);
  }

  /**
   * Sends a request with the given configuration and options, and returns a Promise that resolves to the response.
   *
   * @param {AxiosRequestConfig} config - The configuration for the request.
   * @param {RequestOptions} [options] - The options for the request.
   * @returns {Promise<T>} A Promise that resolves to the response data.
   */
  request<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions,
  ): Promise<T> {
    // 克隆配置
    let conf: CreateAxiosOptions = cloneDeep(config);
    // 获取转换器
    const transform = this.getTransform();
    // 获取请求选项
    const { requestOptions } = this.options;
    // 合并请求选项
    const opt: RequestOptions = Object.assign({}, requestOptions, options);
    // 获取钩子函数
    const { beforeRequestHook, requestCatchHook, transformRequestHook } =
      transform || {};
    // 如果存在并且是函数，则执行 beforeRequestHook 钩子函数
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = beforeRequestHook(conf, opt);
    }
    // 将请求选项设置到配置中
    conf.requestOptions = opt;
    // 处理支持 FormData 的情况
    conf = this.supportFormData(conf);
    // 返回一个 Promise 对象
    return new Promise((resolve, reject) => {
      // 发起请求
      this.axiosInstance
        .request<any, AxiosResponse<Result>>(conf)
        .then((res: AxiosResponse<Result>) => {
          // 如果存在并且是函数，则执行 transformRequestHook 钩子函数
          if (transformRequestHook && isFunction(transformRequestHook)) {
            try {
              const ret = transformRequestHook(res, opt);
              resolve(ret);
            } catch (err) {
              reject(err || new Error("request error!"));
            }
            return;
          }
          resolve(res as unknown as Promise<T>);
        })
        .catch((e: Error | AxiosError) => {
          // 如果存在并且是函数，则执行 requestCatchHook 钩子函数
          if (requestCatchHook && isFunction(requestCatchHook)) {
            reject(requestCatchHook(e, opt));
            return;
          }
          // 如果是 AxiosError 类型的错误，则在这里重写错误消息
          if (axios.isAxiosError(e)) {
            // 重写来自 axios 的错误消息
          }
          reject(e);
        });
    });
  }
}
